package eu.ahsx.limboa.media
{
	import eu.ahsx.limboa.utils.Params;
	import eu.ahsx.limboa.utils.Token;
	
	import flash.utils.Dictionary;
	
	/**
	 * 	Define a sequence of file to load.
	 * 	
	 * 	@author Masy Alexandre
	 * */
	public class MediaSequence
	{
		/**
		 * 	List of all the bundles contained in the application
		 * */
		private static var bundles:Dictionary = new Dictionary();
		
		/**
		 * 	Name of the sequence
		 * */
		public var name:String = "";
		
		/**
		 * 	Core used for the sequence
		 * */
		protected var core:MediaCore;
		
		/**
		 * 	Number of file loaded
		 * */
		protected var index:int;
		
		/**
		 * 	Number of file left to load
		 * */
		protected var left:int;
		
		/**
		 * 	Number of file to load
		 * */
		protected var max:int;
		
		/**
		 * 	Start functions
		 * */
		protected var start:Array;
		
		/**
		 * 	Init functions
		 * */
		protected var init:Array;
		
		/**
		 * 	Progress functions
		 * */
		protected var progress:Array;
		
		/**
		 * 	Complete functions
		 * */
		protected var complete:Array;
		
		/**
		 * 	Done functions
		 * */
		protected var done:Array;
		
		/**
		 * 	Error functions
		 * */
		protected var error:Array;
		
		/**
		 * 	Bundle of files
		 * */
		protected var bundle:Array;
		
		/**
		 * 	Currently loading
		 * */
		protected var loading:Boolean = false;
		
		/**
		 * 	Is the loading process running ?
		 * 	Has the load been authorized by starting it ?
		 * */
		protected var running:Boolean = false;
		
		/**
		 * 	Waiting files
		 * */
		protected var wait:Array;
		
		/**
		 * 	File being loaded
		 * */
		protected var currentFile:MediaFile;
		
		/**
		 * 	Percent of the loading
		 * */
		protected var _percent:Number = 0;
		
		
		/**
		 * 	Retrieve a sequence by a name
		 * 
		 * 	@param name String	The sequence name
		 * 	@return AssetsSequence
		 * */
		public static function getByName(name:String):MediaSequence
		{
			var ret:MediaSequence;
			
			if (bundles[name])
				ret = bundles[name];
			
			return ret;
		}
		
		/**
		 * 	Constructor
		 * 	Create a new sequence.
		 * 
		 * 	@param name String	Name of the sequence
		 * */
		public function MediaSequence(name:String):void
		{
			if (name == null)
				throw new Error("The sequence have to be named");
			
			this.name = name;
			this.core = MediaCore.getCore();
			
			this.max = 0;
			this.left = 0;
			this.index = 0;
			
			this.start = [];
			this.init = [];
			this.progress = [];
			this.complete = [];
			this.done = [];
			this.error = [];
			this.bundle = [];
			this.wait = [];
			
			addSequence(this);
		}
		
		/**
		 * 	Add a file to the sequence
		 * 
		 * 	@param url String	The url of the file to load
		 * 	@param type String	The type of file
		 * 	@param name String	Each file is named so it is easier to catch it up later.
		 * 	@return Boolean Return true if the file is cued, false if already exists.
		 * */
		public function addFile(url:String, type:String, name:String, params:Object = null):Boolean
		{
			var parameters:Object = {name:name, onInit:onInit, onStart:onStart, onProgress:onProgress, onComplete:onComplete, onError:onError};
			var f:MediaFile = MediaFile.create(url, type, parameters);
		
			if (params != null)
			{
				var c:Function;
				
				c = Params.getOrNull("onInit", params);
				if (c != null)
					f.addInit(c);
				
				c = Params.getOrNull("onStart", params);
				if (c != null)
					f.addStart(c);
				
				c = Params.getOrNull("onProgress", params);
				if (c != null)
					f.addProgress(c);
				
				c = Params.getOrNull("onComplete", params);
				if (c != null)
					f.addComplete(c);
				
				c = Params.getOrNull("onDone", params);
				if (c != null)
					f.addDone(c);
				
				c = Params.getOrNull("onError", params);
				if (c != null)
					f.addError(c);
			}
			
			if (f.type == MediaFileType.FONT)
				f.params.fontname = name;

			var ret:Boolean = true; //AssetsCore.load(f, core);
			wait.push( f );
			
			if (ret)
			{
				max++;
				left++;
			}
			
			process();
			
			return ret;
		}
		
		/**
		 * 	Return the current percentage of the loading
		 * 
		 * 	@return Number
		 * */
		public function get percent():Number
		{
			return _percent;
		}
		
		/**
		 * 	Add an Init callback
		 * 	
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function addInit(callback:Function):void
		{
			if (this.init.indexOf(callback)==-1)
				this.init.push( callback );
		}
		
		/**
		 * 	Remove an init callback
		 * 
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function removeInit(callback:Function):void
		{
			var index:int = this.init.indexOf(callback);
			if (index > -1)
				this.init = this.init.slice(index, 1);
		}
		
		/**
		 * 	Add an Start callback
		 * 	
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function addStart(callback:Function):void
		{
			if (this.start.indexOf(callback)==-1)
				this.start.push( callback );
		}
		
		/**
		 * 	Remove an start callback
		 * 
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function removeStart(callback:Function):void
		{
			var index:int = this.start.indexOf(callback);
			if (index > -1)
				this.start = this.start.slice(index, 1);
		}
		
		/**
		 * 	Add an Progress callback
		 * 	
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function addProgress(callback:Function):void
		{
			if (this.progress.indexOf(callback)==-1)
				this.progress.push( callback );
		}
		
		/**
		 * 	Remove an progress callback
		 * 
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function removeProgress(callback:Function):void
		{
			var index:int = this.progress.indexOf(callback);
			if (index > -1)
				this.progress = this.progress.slice(index, 1);
		}
		
		/**
		 * 	Add an Complete callback
		 * 	
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function addComplete(callback:Function):void
		{
			if (this.complete.indexOf(callback)==-1)
				this.complete.push( callback );
		}
		
		/**
		 * 	Remove an complete callback
		 * 
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function removeComplete(callback:Function):void
		{
			var index:int = this.complete.indexOf(callback);
			if (index > -1)
				this.complete = this.complete.slice(index, 1);
		}
		
		/**
		 * 	Add an Done callback
		 * 	
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function addDone(callback:Function):void
		{
			if (this.done.indexOf(callback)==-1)
				this.done.push( callback );
		}
		
		/**
		 * 	Remove an done callback
		 * 
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function removeDone(callback:Function):void
		{
			var index:int = this.done.indexOf(callback);
			if (index > -1)
				this.done = this.done.slice(index, 1);
		}
		
		/**
		 * 	Add an Error callback
		 * 	
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function addError(callback:Function):void
		{
			if (this.error.indexOf(callback)==-1)
				this.error.push( callback );
		}
		
		/**
		 * 	Remove an error callback
		 * 
		 * 	@param callback Function
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function removeError(callback:Function):void
		{
			var index:int = this.error.indexOf(callback);
			if (index > -1)
				this.error = this.error.slice(index, 1);
		}
		
		/**
		 * 	Retrieve a file by the given name at the loading
		 * 
		 * 	@param name String	Name to look for
		 * 	@return AssetsFile
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function getFileByName(name:String):MediaFile
		{
			var ret:MediaFile;
			var n:int = this.bundle.length;
			var f:MediaFile;
			while(--n>=0)
			{
				f = this.bundle[n];
				if (f.name === name)
				{
					ret = f;
					break;
				}
			}
			
			return ret;
		}
		
		/**
		 * 	Get the content of a file by his name
		 * 
		 * 	@param name String	The given name at the loading
		 * 	@return * Return the content of the file
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function getContentByName(name:String):*
		{
			var f:MediaFile = getFileByName(name);
			
			if (f)
				return f.getContent();
			else
				return null;
		}
		
		/**
		 * 	Retrieve a file by the given url
		 * 
		 * 	@param url String	url to look for
		 * 	@return AssetsFile
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function getFileByURL(url:String):MediaFile
		{
			var ret:MediaFile;
			var n:int = this.bundle.length;
			var f:MediaFile;
			while(--n>=0)
			{
				f = this.bundle[n];
				if (f.url === url)
				{
					ret = f;
					break;
				}
			}
			
			return ret;
		}
		
		/**
		 * 	Get the content of a file by his url
		 * 
		 * 	@param name String	The given name at the loading
		 * 	@return * Return the content of the file
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function getContentByURL(url:String):*
		{
			var f:MediaFile = getFileByURL(name);
			
			if (f)
				return f.getContent();
			else
				return null;
		}
		
		/**
		 * 	Start the loading of the files
		 * */
		public function play():void
		{
			this.running = true;
			process();
		}
		
		/**
		 * 	Stop the loading of the files
		 *	
		 * 	@param waitForCurrentFile Boolean If true we wait for the end of the file to stop the loading. If not, stop now the loading 
		 * */
		public function stop(waitForCurrentFile:Boolean = false):void
		{
			this.running = false;
			this.loading = waitForCurrentFile;
			
			if (!waitForCurrentFile)
			{
				MediaCore.remove(currentFile, core);
				wait.splice(0, 0, currentFile);
			}
		}
		
		/**
		 * 	Process the queue to start the next file
		 * */
		protected function process():void
		{
			if (!loading && running && wait.length > 0)
			{
				currentFile = wait.shift();
				if (currentFile != null)
					MediaCore.load(currentFile, core);
			}
			else
				loading = false;
		}
		
		/**
		 * 	Handle init of a file
		 * 
		 * 	@param file AssetsFile
		 * 	@private 
		 * */
		protected function onInit(file:MediaFile):void
		{
			if( !loading && running )
			{
				loading = true;
				fireCallback(init);
			}
		}
		
		/**
		 * 	Handle start of a file
		 * 
		 * 	@param file AssetsFile
		 * 	@private
		 * */
		protected function onStart(file:MediaFile):void
		{
			fireCallback(start);
		}
		
		/**
		 * 	Handle progress of the file loading
		 * 
		 * 	@param file AssetsFile
		 * 	@private
		 * */
		protected function onProgress(file:MediaFile):void
		{
			_percent = file.info.percent * (max == 1 ? 1 : (index+1)/max);
			fireCallback(progress);
		}
		
		/**
		 * 	Handle complete of the file loading
		 * 	
		 * 	@param file AssetsFile
		 * 	@private
		 * */
		protected function onComplete(file:MediaFile):void
		{
			bundle.push( file );
			
			left--;
			index++;
			loading = false;
			
			if (index == max)
			{
				fireCallback(complete);
			}
			else
				process();
		}
		
		/**
		 * 	Handle error while file loading
		 * 
		 * 	@param file AssetsFile
		 * 	@private
		 * */
		protected function onError(file:MediaFile):void
		{
			index++;
			left--;
			
			fireCallback(error);
		}
		
		/**
		 * 	Call all the callback 
		 * 
		 * 	@param	list Vector.<Function>
		 * 	@param	file AssetsFile
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function fireCallback(list:Array):void
		{
			var n:int = list.length;
			while(n--)
				list[n](this);
		}
		
		/**
		 * 	Add a sequence to the bundle
		 * 
		 * 	@param sequence AssetsSequence
		 * */
		private static function addSequence(sequence:MediaSequence):void
		{
			if (bundles[sequence.name])
				throw new Error("There's already a sequence named "+sequence.name);
			
			bundles[sequence.name] = sequence;
		}
	}
}